package cn.edu.dgut.css.sai.dgutoauth2.oauth;

import cn.edu.dgut.css.sai.dgutoauth2.oauth.SaiDGUTAuthorizationResponseFilter;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 此过滤器用于识别用户是否是从i莞工App直接进入应用程序登录，如果是的话，因为没有经过{@link OAuth2AuthorizationRequestRedirectFilter}而直接拿到code，
 * 所以在这个过滤器虚拟构造一个{@link OAuth2AuthorizationRequest}。
 * <p>注意：从i莞工App直接进入应用时，回调地址默认带了state参数，值为home。</p>
 *
 * @author sai
 * @since 1.0
 */
final class SaiDGUTAppLoginFilter extends OncePerRequestFilter {

    private final AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
    private static final char PATH_DELIMITER = '/';

    private final ClientRegistrationRepository clientRegistrationRepository;

    SaiDGUTAppLoginFilter(ClientRegistrationRepository clientRegistrationRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        if (isDgutAppLogin(httpServletRequest)) {
            OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode();
            ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("dgut");
            Map<String, Object> attributes = new HashMap<>();
            attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
            String redirectUriStr = expandRedirectUri(httpServletRequest, clientRegistration, "login");

            // 构造重定向参数，在源请求参数的基础上，如果没state的话则增加state，为了适配spring security oauth2.
            MultiValueMap<String, String> parameterMap = SaiDGUTAuthorizationResponseFilter.OAuth2AuthorizationResponseUtils.toMultiMap(httpServletRequest.getParameterMap());
            // 移除from参数，否则会重复执行本过滤器。
            parameterMap.remove("from");
            String state = httpServletRequest.getParameter("state");
            if (!StringUtils.hasText(state)) {
                state = this.stateGenerator.generateKey().replace("-", ".");
                parameterMap.add("state", state);
            }

            // 虚拟构造一个OAuth2AuthorizationRequest，并保存在http session.
            builder
                    .clientId(clientRegistration.getClientId())
                    .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
                    .redirectUri(redirectUriStr)
                    .scopes(clientRegistration.getScopes())
                    .state(state)
                    .attributes(attributes);
            authorizationRequestRepository.saveAuthorizationRequest(builder.build(), httpServletRequest, httpServletResponse);

            String uri = UriComponentsBuilder.fromUriString(httpServletRequest.getRequestURI())
                    .queryParams(parameterMap)
                    .encode()
                    .build()
                    .toUriString();
            System.out.println(uri+"--------------------------------");
            this.redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, uri);
            return;
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    private boolean isDgutAppLogin(HttpServletRequest request) {
        String mark = request.getParameter("from");
        return "DGUTAPP".equals(mark);
    }

    @SuppressWarnings("SameParameterValue")
    private static String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration, String action) {
        Map<String, String> uriVariables = new HashMap<>();
        uriVariables.put("registrationId", clientRegistration.getRegistrationId());

        UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
                .replacePath(request.getContextPath())
                .replaceQuery(null)
                .fragment(null)
                .build();
        String scheme = uriComponents.getScheme();
        uriVariables.put("baseScheme", scheme == null ? "" : scheme);
        String host = uriComponents.getHost();
        uriVariables.put("baseHost", host == null ? "" : host);
        // following logic is based on HierarchicalUriComponents#toUriString()
        int port = uriComponents.getPort();
        uriVariables.put("basePort", port == -1 ? "" : ":" + port);
        System.out.println(port+"-------------------------------------");
        System.out.println(host);


        String path = uriComponents.getPath();
        if (StringUtils.hasLength(path)) {
            if (path.charAt(0) != PATH_DELIMITER) {
                path = PATH_DELIMITER + path;
            }
        }
        uriVariables.put("basePath", path == null ? "" : path);
        uriVariables.put("baseUrl", uriComponents.toUriString());

        uriVariables.put("action", action == null ? "" : action);

        return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUri())
                .buildAndExpand(uriVariables)
                .toUriString();
    }
}
